package ch.jamme.reference;

import java.lang.reflect.*;
import java.util.*;

import ch.jamme.util.*;

/**
 * Keep tracks of an object reference in the xml document that can't be resolved at the moment.
 * Typically the missing reference will be resolved later during the xml parsing process, so in order to 
 * allow to bind that reference in a second time, it records the point of the graph's object where it is missing.
 * @author Mario Fusco
 */
public class ForwardReference {
	
	public enum ReferenceType {
		SINGLE_FIELD, COLLECTION_FIELD, MAP_KEY_FIELD, MAP_VALUE_FIELD, MAP_BOTH_FIELD;
	}
	
	private ReferenceType type;
	
	private String idRef;
	private String mapKeyIdRef;
	private Field field;
	
	private Class<?> clazz;
	
	private Object plainOwner;
	private Collection<? super Object> collectionOwner;
	private Map<? super Object, ? super Object> mapOwner;
	
	private Object resolvedMapEntry;

	public ForwardReference(String idRef, Field field) {
		this.idRef = idRef;
		this.field = field;
	}
	
	public String getIdRef() {
		return idRef;
	}
	
	public void setAsSingleField(Class<?> clazz, Object owner) {
		type = ReferenceType.SINGLE_FIELD;
		this.clazz = clazz;
		this.plainOwner = owner;
	}
	
	public void setAsCollectionField(Collection<? super Object> owner) {
		type = ReferenceType.COLLECTION_FIELD;
		this.collectionOwner = owner;
	}
	
	public void setAsMapBothFields(Map<? super Object, ? super Object> owner, String mapKeyIdRef) {
		this.type = ReferenceType.MAP_BOTH_FIELD;
		this.mapOwner = owner;
		this.mapKeyIdRef = mapKeyIdRef;
	}
	
	public void setAsMapKeyField(Map<? super Object, ? super Object> owner, Object resolvedMapValue) {
		this.type = ReferenceType.MAP_KEY_FIELD;
		this.mapOwner = owner;
		this.resolvedMapEntry = resolvedMapValue;
	}
	
	public void setAsMapValueField(Map<? super Object, ? super Object> owner, Object resolvedMapKey) {
		this.type = ReferenceType.MAP_VALUE_FIELD;
		this.mapOwner = owner;
		this.resolvedMapEntry = resolvedMapKey;
	}
	
	/**
	 * Bound a missing reference with its corresponding forward resolved one
	 * @param resolvedObjects All the objects, mapped by their reference id, resolved during the xml parsing process
	 * @return true if the missing reference has been actually resolved and bound to the objects graph
	 * @throws Exception
	 */
	public boolean resolve(Map<String, Object> resolvedObjects) throws Exception {
		if (type == null) {
			if (Logger.isDebugEnabledFor(this)) Logger.debug(this, "Skipping void reference");
			return true;
		}
		Object value = resolvedObjects.get(idRef);
		if (value == null) return false;
		if (Logger.isDebugEnabledFor(this)) Logger.debug(this, "Resolving " + this + " with " + value);
		switch (type) {
			case SINGLE_FIELD:
				IntrospectionUtil.setFieldValue(clazz, field, plainOwner, value);
				break;
			case COLLECTION_FIELD:
				collectionOwner.add(value);
				break;
			case MAP_KEY_FIELD:
				mapOwner.put(value, resolvedMapEntry);
				break;
			case MAP_VALUE_FIELD:
				mapOwner.put(resolvedMapEntry, value);
				break;
			case MAP_BOTH_FIELD:
				mapOwner.put(resolvedObjects.get(mapKeyIdRef), value);
				break;
		}
		return true;
	}
	
	@Override
	public String toString() {
		return type + " for reference " + idRef;
	}
}
